//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/python.h>

#include <buola/app/ceventloop.h>
#include <buola/io/curi.h>
#include <buola/io/cfile.h>

#include <buola/python/cmoduleinitializer.h>
#include <buola/python/types.h>

#include <cwctype>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"

namespace buola { namespace python {

static bool sInit=false;

static wchar_t *sArgV[2]={nullptr,nullptr};
static int sArgC=1;

recursive_mutex CLock::mPythonMutex;

void python_init()
{
    if(sInit) return;

    Py_InitializeEx(0); //we don't want python to handle signals
    PyEval_InitThreads();
    sArgV[0]=wcsdup(L"");
    PySys_SetArgv(sArgC,sArgV);

    sInit=true;

    CModuleInitializer::RunAll();
    ///\todo check why this was here
    //PyEval_ReleaseLock();
}

void python_finish()
{
    if(!sInit) return;
    Py_Finalize();
    sInit=false;
}

void run_code(const std::string &pString,const std::string &pFilename)
{
    YCode lCode;
    if(pFilename.empty())
    {
        lCode=YCode(Py_CompileString(pString.c_str(),"<input>",Py_file_input));
    }
    else
    {
        lCode=YCode(Py_CompileString(pString.c_str(),pFilename.c_str(),Py_file_input));
    }

    if(lCode.IsNull())
    {
        YObject lType,lValue,lTraceback;
        fetch_error(lType,lValue,lTraceback);

        if(lType==PyExc_SyntaxError||lType==PyExc_IndentationError)
        {
            throw XSyntaxError(lValue.GetAttribute("msg").ToString(),
                                lValue.GetAttribute("lineno").ToInt(),
                                lValue.GetAttribute("offset").ToInt(),
                                lValue.GetAttribute("filename").ToString());
        }
    }
    else if(lCode.IsNone())
    {
        msg_warn() << "lCode is none --> incomplete code\n";
        PyErr_Clear();
    }
    else
    {
        YModule lMain=get_main_module();
        YDict lDict=lMain.GetDict();
        lCode.Evaluate(lDict,lDict);

        if(PyErr_Occurred())
        {
            YObject lType,lValue,lTraceback;
            fetch_error(lType,lValue,lTraceback);

            if(lType==PyExc_SyntaxError)
            {
                throw XSyntaxError(lValue.GetAttribute("msg").ToString(),
                                    lValue.GetAttribute("lineno").ToInt(),
                                    lValue.GetAttribute("offset").ToInt(),
                                    lValue.GetAttribute("filename").ToString());
            }
            else
            {
                throw XRuntimeError(lType.GetRepr(),
                                lValue.GetAttribute("message").ToString());
            }
        }
    }
}

void run_file(const io::CURI &pURI)
{
    io::CFile lFile(pURI);

    std::size_t lSize=lFile.Size();
    char lData[lSize+1];

    io::PStream lStream=lFile.GetStream(io::EMode::OPEN|io::EMode::READ);
    lStream->Read((uint8_t*)lData,lSize);
    lData[lSize]=0;

    run_code(lData,pURI.NameWithoutExtension());
}

void run_interactive()
{
    PyRun_InteractiveLoop(stdin,"buola");
}

bool fetch_error(YObject &pType,YObject &pValue,YObject &pTraceback,
                            bool pNormalize)
{
    PyObject *lType,*lValue,*lTraceback;

    PyErr_Fetch(&lType,&lValue,&lTraceback);

    if(lType&&pNormalize)
    {
        PyErr_NormalizeException(&lType,&lValue,&lTraceback);
    }

    pType=lType;
    pValue=lValue;
    pTraceback=lTraceback;

    if(lType)
    {
        pType.DecRef();
        pValue.DecRef();
        pTraceback.DecRef();
        return true;
    }
    else
    {
        return false;
    }
}

YModule get_imported_module(const std::string &pName)
{
    return YModule(PyImport_AddModule(pName.c_str()));
}

YModule get_main_module()
{
    return YModule(PyImport_AddModule("__main__"));
}

YModule get_builtins_module()
{
    return YModule(get_main_module().GetAttribute("__builtins__"));
}

YModule import_module(const std::string &pName)
{
    return YModule(PyImport_ImportModule(pName.c_str()),false);
}

YModule import_source_module(const io::CURI &pURI,const std::string &pName)
{
    python_init();
    static int lNumNum;
    lNumNum++;

    YModule lCodeModule=import_module("imp");
    YCallable lLoadSource=lCodeModule.GetAttribute("load_source");

    std::string lName(pName);

    if(lName.empty())
    {
        lName=pURI.Name();
        lName.erase(lName.find('.'));
    }

    YModule lModule(lLoadSource.Call(YObject(lName),
                                        YObject(pURI.Path())));

    if(lModule.IsNull())
    {
        YObject lValue,lTraceback;
        YType lType;
        fetch_error(lType,lValue,lTraceback);

        if(lType.IsSubTypeOf(PyExc_SyntaxError))
        {
            throw XSyntaxError(lValue.GetAttribute("msg").ToString(),
                                lValue.GetAttribute("lineno").ToInt(),
                                lValue.GetAttribute("offset").ToInt(),
                                lValue.GetAttribute("filename").ToString());
        }
        else
        {
            throw XRuntimeError(lType.GetRepr(),lValue.GetAttribute("message").ToString());
        }
    }

    get_main_module().AddObject(lName,lModule);

    return lModule;
}

void add_to_search_path(const std::string &pPath)
{
    YModule lSys=get_imported_module("sys");
    YList lPath(lSys.GetAttribute("path"));
    lPath.Append(YObject(pPath));
}

void remove_from_search_path(const std::string& /*pPath*/)
{
    ///\todo implement this
    //  YModule lSys("sys");
//  YList lPath=lSys.GetAttribute("path");
}

std::string make_python_identifier(const std::string &pS)
{
    std::wstring lS=utf32(pS);
    
    for(int i=0;i<lS.size();i++)
    {
        if(!iswalnum(lS[i]))
        {
            lS[i]='_';
        }
    }
    
    return utf8(lS);
}

std::wstring make_python_identifier(const std::wstring &pS)
{
    std::wstring lS=pS;
    
    for(int i=0;i<lS.size();i++)
    {
        if(!iswalnum(lS[i]))
        {
            lS[i]='_';
        }
    }
    
    return lS;
}

/*namespace python*/ } /*namespace buola*/ }

#pragma GCC diagnostic pop
